﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Aphysoft.Vate.Graphics
{
    public class Camera : Transformation
    {
        #region Properties

        /// <summary>
        /// Camera view matrix.
        /// </summary>
        private Matrix viewMatrix = Matrix.Identity;
        /// <summary>
        /// Gets camera view matrix.
        /// </summary>
        public Matrix ViewMatrix
        {
            get 
            {
                if (_perf_camera_init ||
                    _perf_camera_position != position ||
                    _perf_camera_up != CameraUp ||
                    _perf_camera_target != CameraTarget)
                {
                    viewMatrix = Matrix.CreateLookAt(Position, CameraTarget, CameraUp);
                    inverseViewMatrix = Matrix.Invert(viewMatrix);                    

                    // update performance variables
                    if (_perf_camera_init) _perf_camera_init = false;
                    _perf_camera_position = position;
                    _perf_camera_target = CameraTarget;
                    _perf_camera_up = CameraUp;
                }

                return viewMatrix; 
            }
        }

        // Performance variables
        private bool _perf_camera_init = true;
        private Vector3 _perf_camera_position = Vector3.Zero;
        private Vector3 _perf_camera_up = Vector3.Up;
        private Vector3 _perf_camera_target = Vector3.Zero;

        /// <summary>
        /// Indicates whether the camera up is defined separately in CameraUp property or from transformation up.
        /// </summary>
        private bool defineCameraUp = true;
        /// <summary>
        /// Gets or sets a value indicating whether the camera up is defined separately in CameraUp property or 
        /// from transformation up.
        /// The default value is true.
        /// </summary>
        public bool DefineCameraUp
        {
            get { return defineCameraUp; }
            set { defineCameraUp = value; }
        }
        /// <summary>
        /// Indicates whether the camera target is defined separately in CameraTarget property or calculated 
        /// by transformation position added by transformation forward.
        /// </summary>
        private bool defineCameraTarget = true;
        /// <summary>
        /// Gets or sets a value indicating whether the camera target is defined separately in CameraTarget property 
        /// or calculated by transformation position added by transformation forward.
        /// The default value is true.
        /// </summary>
        public bool DefineCameraTarget
        {
            get { return defineCameraTarget; }
            set { defineCameraTarget = value; }
        }
        /// <summary>
        /// Camera up.
        /// </summary>
        private Vector3 cameraUp = Vector3.Up;
        /// <summary>
        /// Gets or sets camera up.
        /// </summary>
        public Vector3 CameraUp
        {
            get 
            {
                if (!defineCameraUp)
                    return Up;
                else
                    return cameraUp;
            }
            set 
            {
                if (!defineCameraUp) { } // do nothing
                else
                    cameraUp = Vector3.Normalize(value); 
            }
        }
        /// <summary>
        /// Gets the cosine angle between camera up and camera forward.
        /// </summary>
        public float CosineAngleBetweenCameraUpAndCameraForward
        {
            get
            {
                if (defineCameraTarget)
                    return Vector3.Dot(Vector3.Normalize(Vector3.Subtract(cameraTarget, position)), CameraUp);
                else
                    return Vector3.Dot(Forward, CameraUp);
            }
        }
        /// <summary>
        /// Camera inverse view matrix.
        /// </summary>
        private Matrix inverseViewMatrix = Matrix.Identity;
        /// <summary>
        /// Gets camera inverse view matrix.
        /// </summary>
        public Matrix InverseViewMatrix
        {
            get { return inverseViewMatrix; }
        }
        /// <summary>
        /// Camera look target.
        /// </summary>
        private Vector3 cameraTarget = Vector3.Zero;
        /// <summary>
        /// Gets or sets camera target.
        /// </summary>
        public Vector3 CameraTarget
        {
            get 
            {
                if (!defineCameraTarget)
                    return Vector3.Add(Position, Forward);
                else
                    return cameraTarget; 
            }
            set 
            {
                if (!defineCameraTarget) { } // do nothing
                else
                    cameraTarget = value; 
            }
        }
        /// <summary>
        /// Gets camera normal forward, calculated from cross product between CameraUp and Camera transformation Right.
        /// </summary>
        public Vector3 CameraNormalForward
        {
            get
            {
                return Vector3.Normalize(Vector3.Cross(CameraUp, Right));
            }
        }
        /// <summary>
        /// Gets camera normal right, calculated from cross product between Camera transformation forward and CameraUp.
        /// </summary>
        public Vector3 CameraNormalRight
        {
            get
            {
                return Vector3.Normalize(Vector3.Cross(Forward, CameraUp));
            }
        }


        /// <summary>
        /// The name of the camera.
        /// </summary>
        private string name;
        /// <summary>
        /// Gets the name of the camera.
        /// </summary>
        public string Name
        {
            get { return name; }
            internal set { name = value; }
        }
        #endregion

        #region Constructor
        /// <summary>
        /// Create new instance of Camera.
        /// </summary>
        /// <param name="name">The name of camera.</param>
        public Camera(string name)
        {
            this.name = name;

            CameraManager.Add(name, this);
        }
        #endregion
    }
}
